home *** CD-ROM | disk | FTP | other *** search
/ Openstep 4.2 (Developer) / Openstep Developer 4.2.iso / NextDeveloper / Source / GNU / uucp / Uucp.framework / unix.subproj / spawn.c < prev    next >
Encoding:
C/C++ Source or Header  |  1995-10-09  |  11.5 KB  |  454 lines

  1. /* spawn.c
  2.    Spawn a program securely.
  3.  
  4.    Copyright (C) 1992, 1993, 1994, 1995 Ian Lance Taylor
  5.  
  6.    This file is part of the Taylor UUCP package.
  7.  
  8.    This program is free software; you can redistribute it and/or
  9.    modify it under the terms of the GNU General Public License as
  10.    published by the Free Software Foundation; either version 2 of the
  11.    License, or (at your option) any later version.
  12.  
  13.    This program is distributed in the hope that it will be useful, but
  14.    WITHOUT ANY WARRANTY; without even the implied warranty of
  15.    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  16.    General Public License for more details.
  17.  
  18.    You should have received a copy of the GNU General Public License
  19.    along with this program; if not, write to the Free Software
  20.    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
  21.  
  22.    The author of the program may be contacted at ian@airs.com or
  23.    c/o Cygnus Support, 48 Grove Street, Somerville, MA 02144.
  24.    */
  25.  
  26. #include "uucp.h"
  27.  
  28. #include "uudefs.h"
  29. #include "sysdep.h"
  30.  
  31. #include <errno.h>
  32.  
  33. #if HAVE_FCNTL_H
  34. #include <fcntl.h>
  35. #else
  36. #if HAVE_SYS_FILE_H
  37. #include <sys/file.h>
  38. #endif
  39. #endif
  40.  
  41. #ifndef O_RDONLY
  42. #define O_RDONLY 0
  43. #define O_WRONLY 1
  44. #define O_RDWR 2
  45. #endif
  46.  
  47. #ifndef FD_CLOEXEC
  48. #define FD_CLOEXEC 1
  49. #endif
  50.  
  51. #ifndef environ
  52. extern char **environ;
  53. #endif
  54.  
  55. /* Spawn a child in a fairly secure fashion.  This returns the process
  56.    ID of the child or -1 on error.  It takes far too many arguments:
  57.  
  58.    pazargs -- arguments (element 0 is command)
  59.    aidescs -- file descriptors for stdin, stdout and stderr
  60.    fkeepuid -- TRUE if euid should be left unchanged
  61.    fkeepenv -- TRUE if environment should be left unmodified
  62.    zchdir -- directory to chdir to
  63.    fnosigs -- TRUE if child should ignore SIGHUP, SIGINT and SIGQUIT
  64.    fshell -- TRUE if should try /bin/sh if execve gets ENOEXEC
  65.    zpath -- value for environment variable PATH
  66.    zuu_machine -- value for environment variable UU_MACHINE
  67.    zuu_user -- value for environment variable UU_USER
  68.  
  69.    The aidescs array is three elements long.  0 is stdin, 1 is stdout
  70.    and 2 is stderr.  The array may contain either file descriptor
  71.    numbers to dup appropriately, or one of the following:
  72.  
  73.    SPAWN_NULL -- set descriptor to /dev/null
  74.    SPAWN_READ_PIPE -- set aidescs element to pipe for parent to read
  75.    SPAWN_WRITE_PIPE -- set aidescs element to pipe for parent to write
  76.  
  77.    If fkeepenv is FALSE, a standard environment is created.  The
  78.    environment arguments (zpath, zuu_machine and zuu_user) are only
  79.    used if fkeepenv is FALSE; any of them may be NULL.
  80.  
  81.    This routine expects that all file descriptors have been set to
  82.    close-on-exec, so it doesn't have to worry about closing them
  83.    explicitly.  It sets the close-on-exec flag for the new pipe
  84.    descriptors it returns.  */
  85.  
  86. pid_t
  87. ixsspawn (pazargs, aidescs, fkeepuid, fkeepenv, zchdir, fnosigs, fshell,
  88.       zpath, zuu_machine, zuu_user)
  89.      const char **pazargs;
  90.      int aidescs[3];
  91.      boolean fkeepuid;
  92.      boolean fkeepenv;
  93.      const char *zchdir;
  94.      boolean fnosigs;
  95.      boolean fshell;
  96.      const char *zpath;
  97.      const char *zuu_machine;
  98.      const char *zuu_user;
  99. {
  100.   char *zshcmd;
  101.   int i;
  102.   char *azenv[9];
  103.   char **pazenv;
  104.   boolean ferr;
  105. #if HAVE_FULLDUPLEX_PIPES
  106.   boolean ffullduplex;
  107. #endif
  108.   int ierr = 0;
  109.   int onull;
  110.   int aichild_descs[3];
  111.   int cpar_close;
  112.   int aipar_close[4];
  113.   int cchild_close;
  114.   int aichild_close[3];
  115.   pid_t iret = 0;
  116.   const char *zcmd;
  117.  
  118.   /* If we might have to use the shell, allocate enough space for the
  119.      quoted command before forking.  Otherwise the allocation would
  120.      modify the data segment and we could not safely use vfork.  */
  121.   zshcmd = NULL;
  122.   if (fshell)
  123.     {
  124.       size_t clen;
  125.  
  126.       clen = 0;
  127.       for (i = 0; pazargs[i] != NULL; i++)
  128.     clen += strlen (pazargs[i]);
  129.       zshcmd = zbufalc (2 * clen + i);
  130.     }
  131.  
  132.   /* Set up a standard environment.  This is again done before forking
  133.      because it will modify the data segment.  */
  134.   if (fkeepenv)
  135.     pazenv = environ;
  136.   else
  137.     {
  138.       const char *zterm, *ztz;
  139.       char *zspace;
  140.       int ienv;
  141.  
  142.       if (zpath == NULL)
  143.     zpath = CMDPATH;
  144.  
  145.       azenv[0] = zbufalc (sizeof "PATH=" + strlen (zpath));
  146.       sprintf (azenv[0], "PATH=%s", zpath);
  147.       zspace = azenv[0] + sizeof "PATH=" - 1;
  148.       while ((zspace = strchr (zspace, ' ')) != NULL)
  149.     *zspace = ':';
  150.     
  151.       azenv[1] = zbufalc (sizeof "HOME=" + strlen (zSspooldir));
  152.       sprintf (azenv[1], "HOME=%s", zSspooldir);
  153.  
  154.       zterm = getenv ("TERM");
  155.       if (zterm == NULL)
  156.     zterm = "unknown";
  157.       azenv[2] = zbufalc (sizeof "TERM=" + strlen (zterm));
  158.       sprintf (azenv[2], "TERM=%s", zterm);
  159.  
  160.       azenv[3] = zbufcpy ("SHELL=/bin/sh");
  161.   
  162.       azenv[4] = zbufalc (sizeof "USER=" + strlen (OWNER));
  163.       sprintf (azenv[4], "USER=%s", OWNER);
  164.  
  165.       ienv = 5;
  166.  
  167.       ztz = getenv ("TZ");
  168.       if (ztz != NULL)
  169.     {
  170.       azenv[ienv] = zbufalc (sizeof "TZ=" + strlen (ztz));
  171.       sprintf (azenv[ienv], "TZ=%s", ztz);
  172.       ++ienv;
  173.     }
  174.  
  175.       if (zuu_machine != NULL)
  176.     {
  177.       azenv[ienv] = zbufalc (sizeof "UU_MACHINE="
  178.                  + strlen (zuu_machine));
  179.       sprintf (azenv[ienv], "UU_MACHINE=%s", zuu_machine);
  180.       ++ienv;
  181.     }
  182.  
  183.       if (zuu_user != NULL)
  184.     {
  185.       azenv[ienv] = zbufalc (sizeof "UU_USER="
  186.                  + strlen (zuu_user));
  187.       sprintf (azenv[ienv], "UU_USER=%s", zuu_user);
  188.       ++ienv;
  189.     }
  190.  
  191.       azenv[ienv] = NULL;
  192.       pazenv = azenv;
  193.     }
  194.  
  195.   /* Set up any needed pipes.  */
  196.  
  197.   ferr = FALSE;
  198.   onull = -1;
  199.   cpar_close = 0;
  200.   cchild_close = 0;
  201.  
  202. #if HAVE_FULLDUPLEX_PIPES
  203.   ffullduplex = (aidescs[0] == SPAWN_WRITE_PIPE
  204.          && aidescs[1] == SPAWN_READ_PIPE);
  205. #endif
  206.  
  207.   for (i = 0; i < 3; i++)
  208.     {
  209.       if (aidescs[i] == SPAWN_NULL)
  210.     {
  211.       if (onull < 0)
  212.         {
  213.           onull = open ((char *) "/dev/null", O_RDWR);
  214.           if (onull < 0
  215.           || fcntl (onull, F_SETFD,
  216.                 fcntl (onull, F_GETFD, 0) | FD_CLOEXEC) < 0)
  217.         {
  218.           ierr = errno;
  219.           (void) close (onull);
  220.           ferr = TRUE;
  221.           break;
  222.         }
  223.           aipar_close[cpar_close] = onull;
  224.           ++cpar_close;
  225.         }
  226.       aichild_descs[i] = onull;
  227.     }
  228.       else if (aidescs[i] != SPAWN_READ_PIPE
  229.            && aidescs[i] != SPAWN_WRITE_PIPE)
  230.     aichild_descs[i] = aidescs[i];
  231.       else
  232.     {
  233.       int aipipe[2];
  234.  
  235. #if HAVE_FULLDUPLEX_PIPES
  236.       if (ffullduplex && i == 1)
  237.         {
  238.           /* Just use the fullduplex pipe.  */
  239.           aidescs[i] = aidescs[0];
  240.           aichild_descs[i] = aichild_descs[0];
  241.           continue;
  242.         }
  243. #endif
  244.  
  245.       if (pipe (aipipe) < 0)
  246.         {
  247.           ierr = errno;
  248.           ferr = TRUE;
  249.           break;
  250.         }
  251.  
  252.       if (aidescs[i] == SPAWN_READ_PIPE)
  253.         {
  254.           aidescs[i] = aipipe[0];
  255.           aichild_close[cchild_close] = aipipe[0];
  256.           aichild_descs[i] = aipipe[1];
  257.           aipar_close[cpar_close] = aipipe[1];
  258.         }
  259.       else
  260.         {
  261.           aidescs[i] = aipipe[1];
  262.           aichild_close[cchild_close] = aipipe[1];
  263.           aichild_descs[i] = aipipe[0];
  264.           aipar_close[cpar_close] = aipipe[0];
  265.         }
  266.  
  267.       ++cpar_close;
  268.       ++cchild_close;
  269.  
  270.       if (fcntl (aipipe[0], F_SETFD,
  271.              fcntl (aipipe[0], F_GETFD, 0) | FD_CLOEXEC) < 0
  272.           || fcntl (aipipe[1], F_SETFD,
  273.             fcntl (aipipe[1], F_GETFD, 0) | FD_CLOEXEC) < 0)
  274.         {
  275.           ierr = errno;
  276.           ferr = TRUE;
  277.           break;
  278.         }          
  279.     }
  280.     }
  281.  
  282. #if DEBUG > 1
  283.   if (! ferr && FDEBUGGING (DEBUG_EXECUTE))
  284.     {
  285.       ulog (LOG_DEBUG_START, "Forking %s", pazargs[0]);
  286.       for (i = 1; pazargs[i] != NULL; i++)
  287.     ulog (LOG_DEBUG_CONTINUE, " %s", pazargs[i]);
  288.       ulog (LOG_DEBUG_END, "%s", "");
  289.     }
  290. #endif
  291.  
  292.   if (! ferr)
  293.     {
  294.       /* This should really be vfork if available.  */
  295.       iret = ixsfork ();
  296.       if (iret < 0)
  297.     {
  298.       ferr = TRUE;
  299.       ierr = errno;
  300.     }
  301.     }
  302.  
  303.   if (ferr)
  304.     {
  305.       for (i = 0; i < cchild_close; i++)
  306.     (void) close (aichild_close[i]);
  307.       iret = -1;
  308.     }
  309.  
  310.   if (iret != 0)
  311.     {
  312.       /* The parent.  Close the child's ends of the pipes and return
  313.      the process ID, or an error.  */
  314.       for (i = 0; i < cpar_close; i++)
  315.     (void) close (aipar_close[i]);
  316.       ubuffree (zshcmd);
  317.       if (! fkeepenv)
  318.     {
  319.       char **pz;
  320.  
  321.       for (pz = azenv; *pz != NULL; pz++)
  322.         ubuffree (*pz);
  323.     }
  324.       errno = ierr;
  325.       return iret;
  326.     }
  327.  
  328.   /* The child.  */
  329.  
  330. #ifdef STDIN_FILENO
  331. #if STDIN_FILENO != 0 || STDOUT_FILENO != 1 || STDERR_FILENO != 2
  332.  #error The following code makes invalid assumptions
  333. #endif
  334. #endif
  335.  
  336.   for (i = 0; i < 3; i++)
  337.     {
  338.       if (aichild_descs[i] != i)
  339.     (void) dup2 (aichild_descs[i], i);
  340.       /* This should only be necessary if aichild_descs[i] == i, but
  341.      some systems copy the close-on-exec flag for a dupped
  342.      descriptor, which is wrong according to POSIX.  */
  343.       (void) fcntl (i, F_SETFD, fcntl (i, F_GETFD, 0) &~ FD_CLOEXEC);
  344.     }
  345.  
  346.   zcmd = pazargs[0];
  347.   pazargs[0] = strrchr (zcmd, '/');
  348.   if (pazargs[0] == NULL)
  349.     pazargs[0] = zcmd;
  350.   else
  351.     ++pazargs[0];
  352.  
  353.   if (! fkeepuid)
  354.     {
  355.       /* Return to the uid of the invoking user.  */
  356.       (void) setuid (getuid ());
  357.       (void) setgid (getgid ());
  358.     }
  359.   else
  360.     {
  361.       /* Try to force the UUCP uid to be both real and effective user
  362.      ID, in order to present a consistent environment regardless
  363.      of the invoking user.  This won't work on older System V
  364.      based systems, where it can cause trouble if ordinary users
  365.      wind up executing uuxqt, perhaps via uucico; any program
  366.      which uuxqt executes will have an arbitrary real user ID, so
  367.      if the program is itself a setuid program, any security
  368.      checks it does based on the real user ID will be incorrect.
  369.      Fixing this problem would seem to require a special setuid
  370.      root program; I have not used this approach because
  371.      modern systems should not suffer from it.  */
  372. #if HAVE_SETREUID
  373.       (void) setreuid (geteuid (), -1);
  374.       (void) setregid (getegid (), -1);
  375. #else
  376.       (void) setuid (geteuid ());
  377.       (void) setgid (getegid ());
  378. #endif
  379.     }
  380.  
  381.   if (zchdir != NULL)
  382.     (void) chdir (zchdir);
  383.  
  384.   if (fnosigs)
  385.     {
  386. #ifdef SIGHUP
  387.       (void) signal (SIGHUP, SIG_IGN);
  388. #endif
  389. #ifdef SIGINT
  390.       (void) signal (SIGINT, SIG_IGN);
  391. #endif
  392. #ifdef SIGQUIT
  393.       (void) signal (SIGQUIT, SIG_IGN);
  394. #endif
  395.     }
  396.  
  397. #ifdef isc386
  398. #ifdef _POSIX_SOURCE
  399.   /* ISC has a remarkably stupid notion of environments.  If a program
  400.      is compiled in the POSIX environment, it sets a process state.
  401.      If you then exec a program which expects the USG environment, the
  402.      process state is not reset, so the execed program fails.  The
  403.      __setostype call is required to change back to the USG
  404.      environment.  This ought to be a switch in policy.h, but it seems
  405.      too trivial, so I will leave this code here and wait for it to
  406.      break in some fashion in the next version of ISC.  */
  407.   __setostype (0);
  408. #endif
  409. #endif
  410.  
  411.   (void) execve ((char *) zcmd, (char **) pazargs, pazenv);
  412.  
  413.   /* The exec failed.  If permitted, try using /bin/sh to execute a
  414.      shell script.  */
  415.   if (errno == ENOEXEC && fshell)
  416.     {
  417.       char *zto;
  418.       const char *azshargs[4];
  419.       
  420.       pazargs[0] = zcmd;
  421.       zto = zshcmd;
  422.       for (i = 0; pazargs[i] != NULL; i++)
  423.     {
  424.       const char *zfrom;
  425.  
  426.       for (zfrom = pazargs[i]; *zfrom != '\0'; zfrom++)
  427.         {
  428.           /* Some versions of /bin/sh appear to have a bug such
  429.          that quoting a '/' sometimes causes an error.  I
  430.          don't know exactly when this happens (I can recreate
  431.          it on Ultrix 4.0), but in any case it is harmless to
  432.          not quote a '/'.  */
  433.           if (*zfrom != '/')
  434.         *zto++ = '\\';
  435.           *zto++ = *zfrom;
  436.         }
  437.       *zto++ = ' ';
  438.     }
  439.       *(zto - 1) = '\0';
  440.  
  441.       azshargs[0] = "sh";
  442.       azshargs[1] = "-c";
  443.       azshargs[2] = zshcmd;
  444.       azshargs[3] = NULL;
  445.  
  446.       (void) execve ((char *) "/bin/sh", (char **) azshargs, pazenv);
  447.     }
  448.  
  449.   _exit (EXIT_FAILURE);
  450.  
  451.   /* Avoid compiler warning.  */
  452.   return -1;
  453. }
  454.